/*
 * Copyright (c) 2014, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.omorphdb.core.index;

import java.io.IOException;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

import org.omorphdb.core.index.hash.HashFunctionFactory;
import org.omorphdb.core.index.hash.HashIndex;
import org.omorphdb.core.index.unique.DefaultIndex;
import org.omorphdb.core.io.Resource;
import org.omorphdb.core.io.ResourceFinder;
import org.omorphdb.core.value.ValueType;

/**
 * Utility class which provides functionality to quickly access all indexes.
 * 
 * @author Marco Tamburelli
 */
public class Indexes
{
	private final ResourceFinder resourceFinder;

	private final DefaultIndex defaultIdx;
	private final Map<Integer, Map<String, EnumMap<IndexType, ChainedIndex<?>>>> indexMap;

	/**
	 * Constructor.
	 * 
	 * @param resourceFinder a {@link ResourceFinder} which allow to generate
	 *        index by proper resource.
	 * @throws IOException
	 */
	public Indexes(ResourceFinder resourceFinder) throws IOException
	{
		this.resourceFinder = resourceFinder;
		this.defaultIdx = new DefaultIndex(this.resourceFinder.acquireDefaultIndex());

		this.indexMap = new HashMap<>();
	}

	/**
	 * Returns the default index.
	 * 
	 * @return
	 */
	public DefaultIndex getDefaultIndex()
	{
		return this.defaultIdx;
	}

	/**
	 * Returns (and creates in case it didn't exist yet) the index matching
	 * provided parameters.
	 * 
	 * @param classID
	 * @param field
	 * @param fieldType
	 * @param indexType
	 * @return
	 * @throws IOException
	 */
	public <T> ChainedIndex<T> getIndex(int classID, String field, ValueType fieldType, IndexType indexType)
			throws IOException
	{
		String resourceName = classID + "." + field + "." + (char) fieldType.getByteType();

		Map<String, EnumMap<IndexType, ChainedIndex<?>>> fieldMap = this.indexMap.get(classID);

		if (fieldMap == null)
		{
			fieldMap = new HashMap<String, EnumMap<IndexType, ChainedIndex<?>>>();
			this.indexMap.put(classID, fieldMap);
		}

		EnumMap<IndexType, ChainedIndex<?>> idxTypeMap = fieldMap.get(field);

		if (idxTypeMap == null)
		{
			idxTypeMap = new EnumMap<IndexType, ChainedIndex<?>>(IndexType.class);
			fieldMap.put(field, idxTypeMap);
		}

		@SuppressWarnings("unchecked")
		ChainedIndex<T> chainedIndex = (ChainedIndex<T>) idxTypeMap.get(indexType);

		if (chainedIndex == null)
		{
			switch (indexType)
			{
			case HASH:
				Resource resource = this.resourceFinder.acquire(resourceName + "H");
				chainedIndex = new HashIndex<>(resource, new HashFunctionFactory<T>(fieldType));
				idxTypeMap.put(indexType, chainedIndex);

				break;

			case DEFAULT:
			case SORTED:
			default:
				return null;
			}
		}

		return chainedIndex;
	}

	/**
	 * Returns a map containing indexes defined for the given parameters,
	 * grouped by index type.
	 * 
	 * @param classID
	 * @param field
	 * @return
	 */
	public EnumMap<IndexType, ChainedIndex<?>> getIndexes(int classID, String field)
	{
		Map<String, EnumMap<IndexType, ChainedIndex<?>>> fieldMap = this.indexMap.get(classID);

		if (fieldMap == null) return new EnumMap<IndexType, ChainedIndex<?>>(IndexType.class);

		EnumMap<IndexType, ChainedIndex<?>> idxTypeMap = fieldMap.get(field);

		return idxTypeMap == null ? new EnumMap<IndexType, ChainedIndex<?>>(IndexType.class) : idxTypeMap.clone();
	}
}
